package com.mswyt.utils.msCrudbean;

import com.alibaba.fastjson.parser.Feature;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.toolkit.SqlHelper;
import com.mswyt.utils.base.MsAssertUtils;
import com.mswyt.utils.base.MsEmptyUtils;
import com.mswyt.utils.base.MsIocUtils;
import com.mswyt.utils.config.cache.MsCache;
import com.mswyt.utils.config.constants.ApplicationConstant;
import com.mswyt.utils.msConverts.MsConvertDataUtils;
import com.mswyt.utils.msConverts.MsEntityUtils;
import com.mswyt.utils.msCrudbean.annotation.MsCrudBean;
import com.mswyt.utils.msCrudbean.annotation.MsCrudRewrite;
import com.mswyt.utils.msCrudbean.dto.MsCrudDto;
import com.mswyt.utils.msHandlebean.annotation.MsDictSelect;
import com.mswyt.utils.msHandlebean.dto.MsDelCheckDto;
import com.mswyt.utils.msHandlebean.dto.MsSaveCheckDto;
import com.mswyt.utils.msMp.MsDb;
import com.mswyt.utils.test.pb.bean.MsDict;
import org.apache.commons.collections4.MapUtils;
import org.springframework.stereotype.Component;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;

import static com.alibaba.fastjson.JSON.parseObject;

@Component
public class MsCrudUtils {
    //缓存targetParam对应的注解信息
    private static Map<String, MsCrudBean> cacheCrudBeanMap = MsCache.crudCacheMap;
    private static Map<String, Set<String>> cacheExcludeFieldMap = MsCache.crudExcludeFieldMap;
    private static Map<String, String> tbBizIdCacheMap = MsCache.tbBizIdCacheMap;
    private static final String DEL_NO_LIST = "删除方法需要在data参数中传入删除的数据集合。data:{'list':[]}";
    private static final String NO_TARGET_PARAM = "请传入targetParam参数值";
    private static final String NO_CURD = "未找到使用@MsCrudBean注解的实体类类";
    private static final String NO_DATA = "data数据不可为空";
    private static final String NO_SERVICE_CLASS = "传入rewriteMethod参数值表示需要重写默认的crud方法，@MsAsync(serviceClazz)必须指定重写方法所在的service层,然后在其类任意方法上配置@MsCrudRewrite即可";
    private static final String NO_SERVICE_CLASS_METHOD = "请根据@MsCrudBean中配置的service层类里的方法上加上@MsCrudRewrite注解，表明是重写的方法";
    private static final String OPT_SUCCESS = "操作成功";

    /**
     * 含*的键名表示必填
     * 通用的删除，包含删除检验功能，需要在实体字段配置@MsDelCheck注解
     *
     * @param dto: {
     *             " *targetParam": "配置在实体类@MsCrudBean中 targetParam的值“
     *             " *data":{"*list":["b1","b2"]},
     *             "config":{"throw":"true/false","check":"true/false"},
     *             "rewriteMethod": "重写方法"
     *             }
     * @return 返回值取决于config的配置。参考MsCrudDto
     * @author mashuai
     */
    public static Object deleteData(MsCrudDto dto) throws Exception {
        Map<String, Object> dataMap = checkData(dto);
        if (!dataMap.containsKey("list")) MsAssertUtils.throwException(DEL_NO_LIST);
        List list = (List) MapUtils.getObject(dataMap, "list");
        //config配置的默认值
        boolean throwMsg = getConfigCheck(dto, "throw");
        boolean check = getConfigCheck(dto, "check");
        //解析出注解信息
        MsCrudBean crudBeanA = getMsCrudBeanByTargetParam(checkTargetParam(dto));
        Class<?> tClass = crudBeanA.entityClazz();
        //1.优先调用自定义删除方法
        String rewriteMethod = dto.getRewriteMethod();
        if (MsEmptyUtils.isNotEmpty(rewriteMethod)) return invokeRewriteMethod(rewriteMethod, crudBeanA, dataMap);
        //2.执行删除前的逻辑检验
        if (check) {
            //throw=true，只要有一条数据被引用了，就会抛出异常
            List<MsDelCheckDto> delCheckResult = invokeDelCheck(list, tClass, throwMsg);
            //throw=false，删除可以删除的数据
            if (list.size() != 0) {
                executeDelMethod(dto,tClass,list);
            }
            return delCheckResult;
        }
        //3.如果没有删除前引用检验，默认传入的集合全部删除
        executeDelMethod(dto,tClass,list);
        return OPT_SUCCESS;
    }

    private static void executeDelMethod(MsCrudDto dto, Class<?> tClass, List list) {
        String bizId = getBizId(dto.getConfig(), tClass);
        //如果根据主键id删除
        if (MsEmptyUtils.isEmpty(bizId)) {
            SqlHelper.execute(tClass, baseMapper -> SqlHelper.retBool(baseMapper.deleteBatchIds(list)));
        }
        //自定义删除字段。
        else {
            QueryWrapper wrapper = new QueryWrapper<>();
            wrapper.in(bizId, list);
            SqlHelper.execute(tClass, baseMapper -> SqlHelper.retBool(baseMapper.delete(wrapper)));
        }

    }

    /**
     * 含*的键名表示必填
     * 通用的保存，包含保存前对某些字段是是否已经存在，进行检验，比如对code、name字段值进行检验。需要在实体字段配置@MsSaveCheck注解
     *
     * @param dto: {
     *             "*targetParam": "msBuild",
     *             "*data":{实体类数据},
     *             "config":{"onAdd":"true/false","check":"true/false","throw":"true/false"},
     *             "rewriteMethod": "重写方法"
     *             }
     * @return 返回值取决于config的配置。参考MsCrudDto
     * @author mashuai
     */
    public static Object save(MsCrudDto dto) throws Exception {
        //获取数据
        Map<String, Object> dataMap = checkData(dto);
        boolean noAdd = getConfigCheck(dto, "noAdd");
        boolean check = getConfigCheck(dto, "check");
        boolean throwMsg = getConfigCheck(dto, "throw");
        //解析出注解信息
        MsCrudBean crudBeanA = getMsCrudBeanByTargetParam(checkTargetParam(dto));
        Class<?> tClass = crudBeanA.entityClazz();
        //1.优先调用自定义保存方法
        String rewriteMethod = dto.getRewriteMethod();
        if (MsEmptyUtils.isNotEmpty(rewriteMethod)) return invokeRewriteMethod(rewriteMethod, crudBeanA, dataMap);
        //获取tClass中的主键id注解
        //把map转成需要的bean
        Object bean = parseObject(MsConvertDataUtils.objToJsonStr(dataMap), tClass, new Feature[0]);
        //2.判断是否进行检验
        if (check) {
            //如果不存在重复的字段，检查结果为0，
            List<MsSaveCheckDto> dtoList = invokeSaveCheck(bean, throwMsg);
            //是否把检查结果进行返回
            if (dtoList.size() != 0 && !throwMsg) {
                return dtoList;
            }
        }
        //3.检验通过后，开始进行保存
        if (!noAdd || MsEmptyUtils.isEmpty(MapUtils.getString(dataMap, getBizId(dto.getConfig(), tClass)))) {
            insertBean(bean);
        } else {
            updateBeanById(bean);
        }
        return bean;
    }

    /**
     * 获取该表的业务主键id字段名称
     **/
    private static String getBizId(Map<String, Object> config, Class<?> tClass) {
        if (config != null) {
            String bizId = MapUtils.getString(config, "bizId");
            if (MsEmptyUtils.isNotEmpty(bizId)) return bizId;
        }
        //是否开启动态获取表主键id
        if ("true".equals(ApplicationConstant.msHbBizIdNameDynamic)) {
            String className = tClass.getName();
            String bizId = tbBizIdCacheMap.get(className);
            if (MsEmptyUtils.isNotEmpty(bizId)) return bizId;
            for (Field field : tClass.getDeclaredFields()) {
                field.setAccessible(true);
                TableField annotation = field.getAnnotation(TableField.class);
                if (annotation != null) {
                    String value = annotation.value();
                    String bizIdName = MsEmptyUtils.isNotEmpty(value) ? MsConvertDataUtils.lineToCamel(value) : field.getName();
                    tbBizIdCacheMap.put(className, bizIdName);
                    return bizIdName;
                }
            }
        }
        //获取默认字段的id
        return ApplicationConstant.msHbBizIdName;
    }

    public static Object find(MsCrudDto dto) throws Exception {
        //获取数据
        Map<String, Object> dataMap = dto.getData();
        if (dataMap == null) dataMap = new HashMap<>();
        boolean handle = getConfigCheck(dto, "handle");
        //解析出注解信息
        MsCrudBean crudBeanA = getMsCrudBeanByTargetParam(checkTargetParam(dto));
        Class<?> tClass = crudBeanA.entityClazz();
        Map<String, Object> configMap = dto.getConfig() == null ? new HashMap<>() : dto.getConfig();
        //1.优先使用自定义方法
        String rewriteMethod = dto.getRewriteMethod();
        if (MsEmptyUtils.isNotEmpty(rewriteMethod)) {
            dataMap.putAll(configMap);
            return invokeRewriteMethod(rewriteMethod, crudBeanA, dataMap);
        }
        //构建查询条件
        QueryWrapper qw = handleQw(dataMap, tClass);
        //2.判断是否是分页查询
        if (dto.getConfig() != null) {
            Integer curPage = MapUtils.getInteger(configMap, "curPage");
            Integer pageSize = MapUtils.getInteger(configMap, "pageSize");
            if (curPage != null && pageSize != 0) {
                IPage page = SqlHelper.execute(tClass, baseMapper -> baseMapper.selectPage(new Page<>(curPage, pageSize), qw));
                List records = page.getRecords();
                if (records.size() != 0 && handle) invokeHandleFindData(records, tClass);
                return page;
            }
        }
        //常规列查询
        List result = SqlHelper.execute(tClass, baseMapper -> baseMapper.selectList(qw));
        //对查询结果进行字段过滤
        Class<?> excludeFieldClass = crudBeanA.excludeFieldClass();
        if (result.size() != 0 && handle) invokeHandleFindData(result, tClass);
        if (!excludeFieldClass.getName().equals(Void.class.getName())) {
            return excludeFieldClass(result, tClass, excludeFieldClass);
        }
        return result;
    }

    public static Object dict(MsCrudDto dto) throws Exception {
        Map<String, Object> dataMap = dto.getData();
        if (dataMap == null) dataMap = new HashMap<>();
        //解析出注解信息
        MsCrudBean crudBeanA = getMsCrudBeanByTargetParam(checkTargetParam(dto));
        Class<?> entityClazz = crudBeanA.entityClazz();
        String rewriteMethod = dto.getRewriteMethod();
        if (MsEmptyUtils.isNotEmpty(rewriteMethod)) {
            return invokeRewriteMethod(rewriteMethod, crudBeanA, dataMap);
        }
        //构建查询条件
        QueryWrapper qw = handleQw(dataMap, entityClazz);
        List list = MsDb.list(qw, entityClazz);
        if (list.size() != 0) {
            return handleDictSelectData(list, entityClazz);
        }
        return list;
    }

    /**
     * 处理下拉框值得映射
     *
     * @param list
     * @param entityClazz
     * @return
     * @author mashuai
     */

    private static List<Map<String, Object>> handleDictSelectData(List list, Class<?> entityClazz) {
        //key=字段名，value=别名
        Map<String, String> aliasMap = new HashMap<>();
        for (Field f : entityClazz.getDeclaredFields()) {
            f.setAccessible(true);
            MsDictSelect annotation = f.getAnnotation(MsDictSelect.class);
            if (annotation == null) continue;
            aliasMap.put(f.getName(), annotation.alias());
        }
        List<Map<String, Object>> resultList = new ArrayList<>();
        List<Map<String, Object>> mapList = MsConvertDataUtils.listBeanToListMap(list);
        //遍历查询的结果
        for (Map<String, Object> map : mapList) {
            //存在key和value的映射
            Map<String, Object> resultMap = new HashMap<>();
            boolean hashAlias = false;
            //所有别名值全部塞入到resultMap
            for (String fieldName : map.keySet()) {
                //获取字段对应的别名
                String alias = aliasMap.get(fieldName);
                //如果别名为null，跳出
                if (alias == null) continue;
                hashAlias = true;
                resultMap.put(alias, MapUtils.getObject(map, fieldName));
            }
            if (hashAlias) resultList.add(resultMap);
        }
        return resultList;
    }

    /**
     * 把需要排除的字段值，从集合中移除
     *
     * @param result            查询结果
     * @param entityClass       实体
     * @param excludeFieldClass 排除字段所在的class
     * @return
     * @author mashuai
     */

    private static List<Map<String, Object>> excludeFieldClass(List<?> result, Class<?> entityClass, Class<?> excludeFieldClass) {
        String entityClassName = entityClass.getName();
        Set<String> diffSet = cacheExcludeFieldMap.get(entityClassName);
        if (diffSet == null || diffSet.size() == 0) {
            diffSet = MsEntityUtils.getSameFields(entityClass, excludeFieldClass);
            cacheExcludeFieldMap.put(entityClassName, diffSet);
        }
        List<Map<String, Object>> list = MsConvertDataUtils.listBeanToListMap(result);
        //获取相同的字段，移除
        for (Map<String, Object> map : list) {
            for (String key : diffSet) {
                map.remove(key);
            }
        }
        return list;
    }


    //TODO 公用方法

    /**
     * 检查必填参数是否存在
     **/
    private static String checkTargetParam(MsCrudDto dto) {
        String targetParam = dto.getTargetParam();
        MsAssertUtils.objIsNull(targetParam, NO_TARGET_PARAM);
        return targetParam;
    }

    /**
     * 检查必填参数是否存在
     **/
    private static Map<String, Object> checkData(MsCrudDto dto) {
        //必填参数检验
        Map<String, Object> dataMap = dto.getData();
        MsAssertUtils.objIsNull(dataMap, NO_DATA);
        return dataMap;
    }

    /**
     * 获取config的boolean类型的数据
     **/
    private static boolean getConfigCheck(MsCrudDto dto, String key) {
        boolean b1 = true;
        Map<String, Object> configMap = dto.getConfig();
        if (configMap != null) {
            Boolean b2 = MapUtils.getBoolean(configMap, key);
            if (b2 != null) {
                b1 = b2;
            }
        }
        return b1;
    }

    /**
     * 删除前，判断是否存在引用删除检验
     **/
    private static List<MsDelCheckDto> invokeDelCheck(List ids, Class<?> entityClazz, boolean throwMsg) throws Exception {
        String[] beanNames = MsIocUtils.getBeanNames(IMsCrudInvoke.class);
        IMsCrudInvoke invoker = (IMsCrudInvoke) MsIocUtils.getBean(beanNames[0], IMsCrudInvoke.class);
        return invoker.invokeDelCheck(ids, entityClazz, throwMsg);
    }

    /**
     * 保存前，判断指定的字段值是否已经被引用
     **/
    private static List<MsSaveCheckDto> invokeSaveCheck(Object m, boolean throwMsg) throws Exception {
        String[] beanNames = MsIocUtils.getBeanNames(IMsCrudInvoke.class);
        IMsCrudInvoke invoker = (IMsCrudInvoke) MsIocUtils.getBean(beanNames[0], IMsCrudInvoke.class);
        return invoker.invokeSaveCheck(m, throwMsg);
    }

    /**
     * 查询后，对数据进行处理
     **/
    private static void invokeHandleFindData(List list, Class<?> clazz) throws Exception {
        String[] beanNames = MsIocUtils.getBeanNames(IMsCrudInvoke.class);
        IMsCrudInvoke invoker = (IMsCrudInvoke) MsIocUtils.getBean(beanNames[0], IMsCrudInvoke.class);
        invoker.invokeHandleFindData(list, clazz);
    }

    /**
     * 执行前拦截,如果需要重写默认的crud方法，@CrudAnnotation(serviceClazz)必须指定所在的service层
     **/
    private static Object invokeRewriteMethod(String rewriteMethod, MsCrudBean msCrud, Map<String, Object> data) throws Exception {
        if (MsEmptyUtils.isNotEmpty(rewriteMethod)) {
            Class<?> serviceClazz = msCrud.serviceClazz();
            if (serviceClazz.getName().equals(Void.class.getName())) MsAssertUtils.throwException(NO_SERVICE_CLASS);
            for (Method method : serviceClazz.getMethods()) {
                MsCrudRewrite methodAnnotation = MsIocUtils.findAnnotation(method, MsCrudRewrite.class);
                if (methodAnnotation != null && methodAnnotation.rewriteMethod().equals(rewriteMethod)) {
                    return method.invoke(MsIocUtils.getClassObj(serviceClazz), data);
                }
            }
            MsAssertUtils.throwException(NO_SERVICE_CLASS_METHOD);
        }
        return null;
    }

    /**
     * 获取targetParam对应的注解信息
     *
     * @param targetParam
     * @param <T>
     * @return 返回目标参数实体上@MsCrudBean注解信息
     */
    private static <T> MsCrudBean getMsCrudBeanByTargetParam(String targetParam) {
        //从缓存获取
        if (cacheCrudBeanMap.containsKey(targetParam)) {
            return cacheCrudBeanMap.get(targetParam);
        }
        //获取所有含@MsCrudBean注解class类----目前版本再启动类加上@MsScan可以进行速度优化
        Map<String, Object> beansMapA = MsIocUtils.getBeansWithAnnotation(MsCrudBean.class);
        MsAssertUtils.objIsNull(beansMapA, NO_CURD);
        for (String key : beansMapA.keySet()) {
            Object classObj = beansMapA.get(key);
            //取出类上@MsCrudBean的信息
            MsCrudBean mA = MsIocUtils.findAnnotation(classObj.getClass(), MsCrudBean.class);
            if (targetParam.equals(mA.targetParam())) {
                cacheCrudBeanMap.put(targetParam, mA);
                return mA;
            }
        }
        MsAssertUtils.throwException("未在实体类上找到注解为@MsAsync(targetParam=" + targetParam + ")的值,无法进行crud托管");
        return null;
    }

    /**
     * 传入一个class字段名称，优先获取@TableField注解里的value值，如果没有@TableField注解，直接把字段转换成下划线
     *
     * @param fieldName:
     * @param entityClazz:
     * @return
     * @author mashuai
     */

    public static String getClassFieldName(String fieldName, Class<?> entityClazz) {
        for (Field field : entityClazz.getDeclaredFields()) {
            field.setAccessible(true);
            if (field.getName().equals(fieldName)) {
                TableField annotation = field.getAnnotation(TableField.class);
                if (annotation != null && MsEmptyUtils.isNotEmpty(annotation.value())) {
                    return annotation.value();
                }
                break;
            }
        }
        return MsConvertDataUtils.camLToUnderline(fieldName);
    }

    /**
     * 插入实体类
     **/
    private static <T> boolean insertBean(T entity) {
        Class<T> entityClass = (Class<T>) entity.getClass();
        Integer result = SqlHelper.execute(entityClass, baseMapper -> baseMapper.insert(entity));
        return SqlHelper.retBool(result);
    }

    /**
     * 根据id进行实体类更新
     **/
    private static <T> boolean updateBeanById(T entity) {
        @SuppressWarnings("unchecked")
        Class<T> entityClass = (Class<T>) entity.getClass();
        return SqlHelper.execute(entityClass, baseMapper -> SqlHelper.retBool(baseMapper.updateById(entity)));
    }

    /**
     * TODO 处理查询条件,默认所有都是like语句
     **/
    private static QueryWrapper handleQw(Map<String, Object> dataMap, Class<?> entityClazz) {
        QueryWrapper qw = new QueryWrapper<>();
        for (String key : dataMap.keySet()) {
            Object val = dataMap.get(key);
            if ("".equals(val)) continue;
            if (!key.contains("@")) {
                qw.eq(getClassFieldName(key, entityClazz), val);
                continue;
            }
            String[] split = key.split("@");
            String entField = split[0];
            String fieldName = getClassFieldName(entField, entityClazz);
            String queryQw = split[1];
            if ("eq".equals(queryQw)) qw.eq(fieldName, val);
            if ("in".equals(queryQw)) qw.in(fieldName, (List) val);
            if ("like".equals(queryQw)) qw.like(fieldName, val);
            //TODO 可以定义更多的查询条件
            if ("lt".equals(queryQw)) qw.lt(fieldName, val);
            if ("le".equals(queryQw)) qw.le(fieldName, val);
            if ("gt".equals(queryQw)) qw.gt(fieldName, val);
            if ("ge".equals(queryQw)) qw.ge(fieldName, val);
            if ("ne".equals(queryQw)) qw.ne(fieldName, val);
            if ("notIn".equals(queryQw)) qw.notIn(fieldName, val);
            if ("between".equals(queryQw)) {
                String v = String.valueOf(val);
                if (v.contains("@")) {
                    String[] vS = v.split("@");
                    qw.between(fieldName, vS[0], vS[1]);
                }
            }
        }
        return qw;
    }

}
